BaseDataEditableModal   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 129
dl 0
loc 189
rs 10
c 0
b 0
f 0
wmc 23

15 Functions

Rating   Name   Duplication   Size   Complexity  
A setDataField 0 6 1
A setData 0 4 1
A ensureSafeClick 0 6 1
A setButtonDisabled 0 6 2
A validate 0 4 1
A componentWillReceiveProps 0 5 2
A getData 0 7 1
A doValidate 0 7 1
A doAdd 0 8 1
A resolveData 0 8 1
B saveData 0 33 6
A doModify 0 8 1
A checkData 0 3 1
A render 0 16 2
A renderChild 0 6 1
1
import React from 'react';
2
import PropTypes from 'prop-types';
3
import { Alert } from 'react-bootstrap';
4
import Modal from './Modal';
5
import { ValidationError } from '../form/ValidationForm';
6
7
export default class BaseDataEditableModal extends React.Component {
8
  constructor(props, defaultData = {}, modes = {}) {
9
    super(props);
10
11
    this.defaultData = defaultData;
12
    this.modes = {
13
      add: {
14
        title: modes.add.title,
15
        buttons: [
16
          {
17
            label: modes.add.buttonLabel || '저장',
18
            onClick: () => this.ensureSafeClick(() => this.saveData()),
19
            style: 'primary',
20
            disabled: false,
21
          },
22
          { label: '닫기', disabled: false, isClose: true },
23
        ],
24
      },
25
      modify: {
26
        title: modes.modify.title,
27
        buttons: [
28
          {
29
            label: modes.modify.buttonLabel || '수정',
30
            onClick: () => this.ensureSafeClick(() => this.saveData()),
31
            style: 'primary',
32
            disabled: false,
33
          },
34
          { label: '닫기', disabled: false, isClose: true },
35
        ],
36
      },
37
    };
38
    this.state = {
39
      title: this.modes[props.mode].title,
40
      mode: props.mode,
41
      buttons: this.modes[props.mode].buttons,
42
      message: '',
43
      messageLevel: 'warning',
44
      saveWarningMessage: '',
45
      data: Object.assign({}, this.defaultData, this.resolveData(props.data)),
46
    };
47
48
    this.ignoreWarning = false; // TODO state로 관리, true일 경우 아예 warning을 발생 안 시키게
49
  }
50
51
  componentWillReceiveProps(props) {
52
    if (props.visible !== this.props.visible && props.visible) {
53
      this.setState({ message: '', saveWarningMessage: '', buttons: this.modes[props.mode].buttons });
54
      this.setData(props.data);
55
    }
56
  }
57
58
  /**
59
   * @abstract
60
   * @returns {object}
61
   */
62
  getData() {
63
    throw new Error('Should implement this method "getData()"');
64
  }
65
66
  /**
67
   * @abstract
68
   * @param {object} data
69
   * @return {object} newData
70
   */
71
  resolveData(data) {
72
    throw new Error('Should implement this method "resolveData()"');
73
  }
74
75
  /**
76
   * @abstract
77
   * @param {object} data
78
   * @returns {Promise}
79
   */
80
  doAdd(data) {
81
    throw new Error('Should implement this method "doAdd()"');
82
  }
83
  /**
84
   * @abstract
85
   * @param {string} id
86
   * @param {object} data
87
   * @returns {Promise}
88
   */
89
  doModify(id, data) {
90
    throw new Error('Should implement this method "doModify()"');
91
  }
92
93
  /**
94
   * @abstract
95
   * @returns {boolean}
96
   */
97
  doValidate() {
98
    throw new Error('Should implement this method "doValidate()"');
99
  }
100
101
  /**
102
   * @abstract
103
   */
104
  renderChild() {
105
    throw new Error('Should implement this method "renderChild()"');
106
  }
107
108
  checkData() {
109
    return true;
110
  }
111
112
  validate() {
113
    const isValid = this.doValidate();
114
    this.setButtonDisabled(!isValid, true);
115
  }
116
117
  setData(data) {
118
    const newData = this.resolveData(data);
119
    this.setState({ data: Object.assign({}, this.defaultData, newData) }, () => this.validate());
120
  }
121
122
  setDataField(field, doAfterSetData = () => {}) {
123
    const newData = Object.assign({}, this.state.data, field);
124
    this.setState({ data: newData }, () => {
125
      doAfterSetData();
126
      this.validate();
127
    });
128
  }
129
130
  setButtonDisabled(disabled, excludeCloseButton, callback) {
131
    const newButtonsState = this.state.buttons.map((button) => {
132
      return (excludeCloseButton && button.isClose) ? button : Object.assign({}, button, { disabled });
133
    });
134
    this.setState({ buttons: newButtonsState }, callback);
135
  }
136
137
  ensureSafeClick(action) {
138
    this.setButtonDisabled(true, false,
139
      () => action()
140
        .then(() => this.setButtonDisabled(false))
141
        .catch(() => this.setButtonDisabled(false)),
142
    );
143
  }
144
145
  saveData() {
146
    const data = this.getData();
147
    return Promise.resolve()
148
      .then(() => this.checkData())
149
      .then(() => (this.props.mode === 'add' ? this.doAdd(data) : this.doModify(this.state.data.id, data)))
150
      .then(() => {
151
        this.props.onSuccess();
152
        this.props.onClose();
153
      })
154
      .catch((error) => {
155
        if (error instanceof ValidationError) {
156
          if (error.level === 'error') {
157
            this.setState({ message: error.message });
158
          } else if (error.level === 'warning') {
159
            this.ignoreWarning = true;
160
            const message = (
161
              <div>
162
                <p>저장하기 전에 아래의 문제(들)를 확인해 주세요.</p>
163
                <ul>{ error.message.split('\n').map(warn => <li>{warn}</li>) }</ul>
164
                <p>그래도 계속 하시려면 <strong>저장 버튼</strong>을 다시 누르세요.</p>
165
              </div>
166
            );
167
            this.setState({ saveWarningMessage: message });
168
          }
169
        } else {
170
          let message = '저장 도중 에러가 발생했습니다. 다시 시도해주세요.';
171
          if (error.response && error.response.data && error.response.data.message) {
172
            message = error.response.data.message;
173
          }
174
          this.setState({ message });
175
        }
176
        throw error;
177
      });
178
  }
179
180
  render() {
181
    return (
182
      <Modal
183
        visible={this.props.visible}
184
        title={this.modes[this.props.mode].title}
185
        buttons={this.state.buttons}
186
        message={this.state.message}
187
        messageLevel={this.state.messageLevel}
188
        onClose={() => this.props.onClose()}
189
      >
190
        { this.renderChild() }
191
        <Alert style={{ display: this.state.saveWarningMessage ? 'block' : 'none' }} bsStyle="warning">
192
          {this.state.saveWarningMessage}
193
        </Alert>
194
      </Modal>
195
    );
196
  }
197
}
198
199
BaseDataEditableModal.defaultProps = {
200
  mode: 'add',
201
  data: null,
202
  onSuccess: () => {},
203
};
204
205
BaseDataEditableModal.propTypes = {
206
  visible: PropTypes.bool.isRequired,
207
  mode: PropTypes.oneOf(['add', 'modify']),
208
  data: PropTypes.object,
209
  onSuccess: PropTypes.func,
210
  onClose: PropTypes.func.isRequired,
211
};
212